#!/usr/bin/env python3
"""
使用pymodbus 3.x版本的多从站Modbus RTU模拟器示例 - 使用稀疏数据块 (Sparse Data Block)
"""

import logging
import time
import sys
import threading
from pymodbus.server import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.framer import FramerType

# 配置日志
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.INFO) # Set to DEBUG for more verbose Pymodbus logs

# def setup_slave_context(slave_id, initial_values=None): # Original sequential version
def setup_slave_context(slave_id):
    """为特定从站创建使用稀疏数据块的数据存储区"""
    # Sparse blocks don't need large initial lists. Addresses are created when set.
    # We use ModbusSparseDataBlock() here.
    spare_dl = {10: [True, True, True, False], 30: True, 40: [True]*20}
    spare_co = {10: [True, True, True, False], 30: True, 40: [True]*20}
    spare_hr = {10: [3, 5, 6, 8], 30: 1, 40: [0]*20}
    spare_ir = {10: [3, 5, 6, 8], 30: 1, 40: [0]*20}
    store = ModbusSlaveContext(
        di=ModbusSparseDataBlock(spare_dl), # Sparse Discrete Inputs
        co=ModbusSparseDataBlock(spare_co), # Sparse Coils
        hr=ModbusSparseDataBlock(spare_hr), # Sparse Holding Registers
        ir=ModbusSparseDataBlock(spare_ir), # Sparse Input Registers
    )
    # The slave_id is handled by the ModbusServerContext when mapping contexts
    return store

def run_updating_server(context):
    """后台线程用于更新模拟数据 (This function remains the same)"""
    slave_1 = 0x01
    slave_2 = 0x02

    counter = 0
    log.info("后台数据更新线程已启动。")
    while True:
        counter += 1
        try:
            # 更新从站1的数据 - setValues works for both sequential and sparse
            # It will create the addresses in the sparse block if they don't exist
            context[slave_1].setValues(3, 10, [counter, counter + 1, counter + 2])  # 保持寄存器 (hr)
            context[slave_1].setValues(4, 10, [counter * 10, counter * 10 + 1])    # 输入寄存器 (ir)
            context[slave_1].setValues(1, 10, [counter % 2 == 0, counter % 3 == 0])  # 线圈 (co)
            context[slave_1].setValues(2, 10, [counter % 2 == 1, counter % 3 == 1])  # 离散输入 (di)
            # log.debug(f"更新从站 {slave_1} 数据, 计数器: {counter}")

            # 更新从站2的数据
            context[slave_2].setValues(3, 0, [counter * 2, counter * 2 + 1])      # 保持寄存器 (hr)
            context[slave_2].setValues(4, 0, [counter * 20, counter * 20 + 1])    # 输入寄存器 (ir)
            context[slave_2].setValues(1, 0, [counter % 3 == 0, counter % 4 == 0])  # 线圈 (co)
            context[slave_2].setValues(2, 0, [counter % 3 == 1, counter % 4 == 1])  # 离散输入 (di)
            # log.debug(f"更新从站 {slave_2} 数据, 计数器: {counter}")

        except Exception as e:
            log.error(f"更新数据时出错: {e}")

        time.sleep(1)  # 每秒更新一次

def run_server():
    """运行Modbus RTU服务器"""
    # 创建从站上下文 (using the modified setup function)
    log.info("创建从站上下文...")
    slave_1_context = setup_slave_context(0x01)
    slave_2_context = setup_slave_context(0x02)

    # 创建多从站的服务器上下文
    slaves = {
        0x01: slave_1_context,
        0x02: slave_2_context
    }
    context = ModbusServerContext(slaves=slaves, single=False)
    log.info("服务器上下文已创建。")

    # 创建设备标识 (remains the same)
    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'https://github.com/pymodbus-dev/pymodbus'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Sparse RTU Server' # Updated model name slightly
    identity.MajorMinorRevision = '3.x.x' # Reflecting general 3.x version

    # 启动后台线程更新数据
    log.info("启动后台数据更新线程...")
    updater = threading.Thread(target=run_updating_server, args=(context,), name="DataUpdater")
    updater.daemon = True # Allow main program to exit even if thread is running
    updater.start()

    # 启动服务器
    log.info("准备启动Modbus RTU服务器...")
    # --- 配置串行端口 ---
    # !! 重要: 修改此处的串口号以匹配您的系统和连接 !!
    # 在Windows上可能是 'COM1', 'COM3' 等
    # 在Linux上可能是 '/dev/ttyUSB0', '/dev/ttyS0' 等
    # 确保您有权限访问该串口
    port = 'COM1'  # <--- 在这里修改串口号
    # port = '/dev/ttyUSB0' # Example for Linux

    baudrate = 9600
    bytesize = 8
    parity = 'N'
    stopbits = 1
    timeout = 1

    log.info(f"服务器参数: Port={port}, Baudrate={baudrate}, Bytesize={bytesize}, Parity={parity}, Stopbits={stopbits}, Timeout={timeout}")

    try:
        log.info(f"启动Modbus RTU服务器在 {port}...")
        StartSerialServer(
            context=context,
            framer=FramerType.RTU, # RTU is the default for StartSerialServer
            identity=identity,
            port=port,
            baudrate=baudrate,
            bytesize=bytesize,
            parity=parity,
            stopbits=stopbits,
            timeout=timeout,
            # ignore_missing_slaves=True # Optional: Set True if requests to undefined slave IDs should be ignored
        )
    except Exception as e:
        log.error(f"无法启动服务器: {e}")
        log.error("请检查:")
        log.error(f"  - 串行端口 '{port}' 是否存在且可用?")
        log.error("  - 是否有权限访问该端口?")
        log.error("  - 端口是否已被其他程序占用?")
        sys.exit(1)

if __name__ == '__main__':
    run_server()
